#ifndef LUAGLUE_H
#define LUAGLUE_H

#include <iostream>
#include <lua.hpp>
#include "script/glue/lua/Common.h"

#include "core/Function.h"

#define lua_boxpointer(L,u) 	(*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))
#define lua_unboxpointer(L,i)   (*(void **)(lua_touserdata(L, i)))

/** \def lua_gettable_number_number
 * \brief Does a x = t[i], where t is on top of the stack. pops to leave stack sane. */
//#define lua_gettable_number_number(L,x,i) lua_pushinteger(L, i); lua_gettable(L, 1); x = lua_tonumber(L, -1); lua_pop(L, 1)
#define lua_gettable_number_i(L,x,i) lua_rawgeti(L, 1, i); x = lua_tonumber(L, -1); lua_pop(L, 1)

/** \def lua_push
 * \brief push into state L, the value x of type X. */
#define lua_push(L, X, x) switch(type_of<X>::object) { \
		case BOOL: lua_pushboolean(L, *reinterpret_cast<bool*>(&x)); break; \
		case INT: lua_pushinteger(L, *reinterpret_cast<int*>(&x)); break; \
		case FLOAT: lua_pushnumber(L, *reinterpret_cast<float*>(&x)); break; \
		case STRING: lua_pushstring(L, x.c_str()); break; \
		case NIL: case UNKNOWN: default: lua_pushnil(L); break; \
		}

//		case STRING: lua_pushstring(L, *reinterpret_cast<const char*>(&x)); break; \

#ifdef DECLARE
#undef DECLARE
#endif

#ifdef END
#undef END
#endif

#define DECLARE(name) const char LuaGlue::name::className[] = #name;
#define META(name) const luaL_reg LuaGlue::name::meta[] = {
#define METHODS(name) END const luaL_reg LuaGlue::name::methods[] = {
#define END	{0,0}};

#define method(class, name) {#name, class::name}

using namespace std;

class App;

void do_lua_func0(int idx);
void do_lua_func(int idx);


template<typename T>
T* unwrap(lua_State* L, int idx)
{
	luaL_checktype(L, idx, LUA_TTABLE);
	lua_getfield(L, idx, "__pointer");
	void* p = lua_touserdata(L, -1);
	lua_pop(L, 1);
	return static_cast<T*>(p);
}


class LuaFunctionHelper
{
public:
	LuaFunctionHelper(::Function fn) : f(fn) {}

	int Execute(lua_State* L);

	static int callback(lua_State* L)
	{
		LuaFunctionHelper* this_ptr = reinterpret_cast<LuaFunctionHelper*>
		                                    (lua_tointeger(L, lua_upvalueindex(1)));
		return this_ptr->Execute(L);
	}
	::Function f;
};

/** \namespace LuaGlue */
namespace LuaGlue
{
	/**
	 * Register the types defined in the external files, uses the Wrapper<T>
	 * template, to call individual ::Register functions.
	 * @param L The Lua state to bind into
	 */
	void Register(lua_State* L);


	/**
	 * \brief Template to ease binding
	 */
	template  <class T>
	class Wrapper
	{
	public:
		static const char className[];
		static const luaL_reg meta[];
		static const luaL_reg methods[];

		/** \brief Register this class with lua */
		static void Register(lua_State* L)
		{
			std::cout << "Registering " << T::className << std::endl;

			lua_newtable (L);

			lua_pushstring(L,"v");
			lua_setfield(L, -2, "__mode");     				// set as weak-value table

			lua_pushvalue(L, -1);             				// duplicate table
			lua_setmetatable(L, -2);          				// set itself as metatable

			lua_replace(L, LUA_REGISTRYINDEX); 				// set table as environment table

			luaL_register(L, T::className, T::methods);     // create method table
			luaL_newmetatable(L, T::className); 			// create metatable for objects
			lua_pushvalue(L, -2);            				// copy the methods to the top of the stack
			lua_setfield(L, -2, "__index");   				// mt.__index = static const luaL_reg methods[]

			luaL_register(L, T::className, T::meta);	    // create metatable
			luaL_newmetatable(L, T::className); 			// create metatable for objects
			lua_pushvalue(L, -2);            				// copy the methods to the top of the stack

			//lua_pushcfunction(L, Wrapper<T>::tostring);
			//lua_setfield(L, -2, "__tostring");

			lua_pushcfunction(L, T::create);				// "static" constructor method
			lua_setfield(L, -2, "new");						//

			lua_setfield(L, -2, "__metatable");   			// mt.__index = static const luaL_reg methods[]

			lua_pushcfunction(L,Wrapper<T>::gc);    		// put the destroy function on the stack
			lua_setfield(L, -2, "__gc");      				// mt.__gc = int gc(L)

			lua_pop(L, 4);
		}

		/** \return The C pointer from lua userdata
		 *  \note Expects a table on the top of the stack */
		static void* Pointer(lua_State* L)
		{
			luaL_checktype(L, 1, LUA_TTABLE);
			lua_getfield(L, 1, "__pointer");
			void* p = lua_touserdata(L, -1);
			lua_pop(L, 1);
			return p;
		}

		/** \brief Push a new instance onto the stack, with ptr as userdata */
		static int Instance(lua_State* L, void* ptr)
		{
			lua_pushlightuserdata(L, ptr);
			lua_rawget(L, LUA_ENVIRONINDEX);
			if(lua_isnil(L, -1))  //object is not in the environment table yet
				{
					lua_pop(L, 1); //get the nil off the stack
					lua_newtable(L); //create a new table

					//FIXME:
					//this needs to be full user data with it's own assigned meta table and
					//__gc function to delete it, otherwise it won't ever get collected and deleted
					lua_pushlightuserdata(L, ptr); 		//push addr
					lua_setfield(L, -2, "__pointer"); 	// table[__pointer]=addr

					lua_pushcfunction(L, Wrapper<T>::tostring);
					lua_setfield(L, -2, "__tostring");

					lua_newtable(L);
					luaL_register(L, 0, T::meta);
					lua_setmetatable(L, -2);

//				luaL_getmetatable(L, T::className); //get the class metatable
//				lua_setmetatable(L, -2);  			//set the class's metatable to the object table

					luaL_register(L, 0, T::methods);
					lua_pushvalue(L, -1);
					lua_setfield(L, -1, "__index");

					lua_pushlightuserdata(L, ptr); 		//push the pointer
					lua_pushvalue(L, -2); 				//push the table

					lua_rawset(L, LUA_ENVIRONINDEX); 	//enviromentTable[addr]=table
				}
			return 1;
		}

		/** \brief Find and push the method name from the static methods[] member
		 *  \return 1 if found, 0 if not */
		static int Method(lua_State* L, const char* name)
		{
			for(int i=0; T::methods[i].name && T::methods[i].func; i++)
				{
					if (strcmp(name, T::methods[i].name) != 0) continue;
					lua_pushcfunction(L, T::methods[i].func);
					return 1;
				}
			return 0;
		}

		/** \brief Returns the className and the userdata pointer */
		static int tostring (lua_State *L)
		{
			char buff[32];

			switch(lua_gettop(L))
				{
				case 0:
					lua_pushstring(L, T::className);
					break;
				case 1:
//					sprintf(buff, "%p", T::check(L, 1));
//					lua_pushfstring(L, "%s: %s", T::className, buff);
					lua_pushstring(L, T::className);
					break;
				default:
					return 0;
					break;
				}
			return 1;
		}

		/** \brief Garbage-Collect */
		static int gc(lua_State *L)
		{
			luaL_checktype(L, 1, LUA_TTABLE);
			lua_getfield(L, 1, "__pointer");
			void* v = lua_touserdata(L, -1);
			reinterpret_cast<T*>(v)->~T();

			lua_pop(L, 1);
			lua_pushnil(L);
			lua_setfield(L, -1, "__pointer");

			return 1;
		}

		/** \brief The Lua "Constructor", implemented in template specializations */
		static int  create(lua_State *L);
	};


}


#endif
